/**
 * Copyright (c) 2012 Nokia Corporation.
 */

package com.nokia.example.tictactoe.ui;

import java.io.IOException;

import javax.microedition.lcdui.Alert;
import javax.microedition.lcdui.Canvas;
import javax.microedition.lcdui.Command;
import javax.microedition.lcdui.CommandListener;
import javax.microedition.lcdui.Display;
import javax.microedition.lcdui.Displayable;
import javax.microedition.lcdui.Graphics;
import javax.microedition.lcdui.Image;

import com.nokia.example.tictactoe.Main;
import com.nokia.example.tictactoe.connectivity.Connection;
import com.nokia.example.tictactoe.connectivity.ConnectionManager;
import com.nokia.example.tictactoe.connectivity.DiscoveryClient;
import com.nokia.example.tictactoe.console.Console;
import com.nokia.example.tictactoe.engine.AbstractGameManager;

/**
 * The main view is shown after the splash screen and it contains all the menus
 * including the game menu.
 */
public class MainView
	extends Canvas
	implements CommandListener,
			   Connection.Listener,
			   TimedProgressBar.Listener
{
	// Constants
	private static final int UNDEFINED = -1;
	private static final int MARGIN = 6;
	private static final int TEXT_LABEL_HEIGHT = 70;
	private static final int BUTTON_COUNT = 3;
	private static final int BUTTON_SPACING = (int)(Button.DEFAULT_HEIGHT * 1.5f);
	private static final int PROGRESS_BAR_WIDTH = 200;
	private static final String APP_IDENTIFIER = "TICTAC"; 
	private static final String SETTINGS_CHANGED_TITLE = "Settings changed";
	private static final String GAME_ONGOING_TITLE = "Game is ongoing";
	
	// States
	private static final int MAIN_MENU = 0;
	private static final int CONNECTING_DIALOG = 1;
	private static final int CONNECTION_ERROR_DIALOG = 2;
	
	// Members
	private Main _midlet = null;
	private final Command _exitAndBackCommand = new Command("Exit", Command.EXIT, 1);
	private final Command _settingsCommand = new Command("Settings", Command.ITEM, 2);
	private final Command _aboutCommand = new Command("About", Command.ITEM, 3);
	private final Command _yesCommand = new Command("YES", Command.OK, 1);
	private final Command _noCommand = new Command("NO", Command.ITEM, 2);
	private final Command _backCommand = new Command("BACK", Command.CANCEL, 3);
	private final Command _okCommand = new Command("OK", Command.OK, 1);
	private final Command _cancelCommand = new Command("CANCEL", Command.CANCEL, 2);
	private ConnectionManager _connectionManager = null;
	private GameView _gameView = null;
	private SettingsView _settingsView = null;
	private AboutView _aboutView = null;
	private Alert _confirmationDialog = null;
	private Image _backgroundImage = null;
	private Image _dimmedBackgroundImage = null;
	private TextLabel _textLabel = null;
	private Button[] _buttons = new Button[BUTTON_COUNT];
	private TimedProgressBar _progressBar = null;
	private Console _console = null;
	private String _latestErrorMessage = null;
	private int _state = UNDEFINED;
	private int _lastButtonPressed = UNDEFINED;
	private final int _buttonY;
	private boolean _initialized = false;

	/**
	 * Constructor.
	 * @param midlet
	 */
	public MainView(Main midlet) {
		if (midlet == null) {
			throw new IllegalArgumentException();
		}
		
		_midlet = midlet;
		
		addCommand(_exitAndBackCommand);
		addCommand(_settingsCommand);
		addCommand(_aboutCommand);
		setCommandListener(this);
		
		_gameView = new GameView();
		_gameView.addCommand(_exitAndBackCommand);
		_gameView.setCommandListener(this);
		
		_buttonY = getHeight() / 2 - BUTTON_SPACING * BUTTON_COUNT / 2;
	}
	
	/**
	 * From Canvas.
	 */
	protected void paint(Graphics graphics) {
		if (!_initialized) {
			return;
		}
		
		final int topLeft = Graphics.TOP | Graphics.LEFT;
		
		// Paint the background
		graphics.drawImage(_backgroundImage, 0, 0, topLeft);
		
		if (_state == CONNECTING_DIALOG
				|| _state == CONNECTION_ERROR_DIALOG)
		{
			graphics.drawImage(_dimmedBackgroundImage, 0, 0, topLeft);
		}
		
		// Paint the buttons
		final int x = (getWidth() - Button.DEFAULT_WIDTH) / 2;
		int y = _buttonY;
		
		for (int i = 0; i < BUTTON_COUNT; ++i) {
			if (_buttons[i] != null) {
				_buttons[i].paint(graphics, x, y);
			}
			
			y += BUTTON_SPACING;
		}
	}

	/**
	 * From Canvas.
	 */	
	protected void pointerPressed(int x, int y) {
		final int buttonIndex = buttonAt(x, y);
		
		if (_lastButtonPressed != UNDEFINED
				&& buttonIndex != _lastButtonPressed
				&& _buttons[_lastButtonPressed] != null)
		{
			_buttons[_lastButtonPressed].setPressed(false);
		}
		
		if (buttonIndex != UNDEFINED && _buttons[buttonIndex] != null) {
			// Some button was pressed
			_buttons[buttonIndex].setPressed(true);
			_lastButtonPressed = buttonIndex;
		}
		
		repaint();
	}

	/**
	 * From Canvas.
	 */	
	protected void pointerReleased(int x, int y) {
		if (_lastButtonPressed != UNDEFINED) {
			final int buttonIndex = buttonAt(x, y);
			
			if (buttonIndex == _lastButtonPressed) {
				// Consider the button clicked
				handleButtonClicked(buttonIndex);
			}
			else if (_buttons[_lastButtonPressed] != null){
				_buttons[_lastButtonPressed].setPressed(false);
			}
			
			repaint();
		}
	}
	
	/**
	 * From CommandListener.
	 */
	public void commandAction(Command command, Displayable displayable) {
		if (command == _exitAndBackCommand) {
			if (displayable == this) {
				// Quit the app
				_midlet.quit();
				return;
			}
			else if (displayable == _gameView) {
				setState(MAIN_MENU, true);
				Display.getDisplay(_midlet).setCurrent(this);
			}
			else if (displayable == _settingsView) {
				if (_settingsView != null && _settingsView.settingsChanged()) {
					// Show the confirmation dialog
					_confirmationDialog = new Alert(SETTINGS_CHANGED_TITLE);
					_confirmationDialog.setString("Apply new settings?");
					_confirmationDialog.addCommand(_yesCommand);
					_confirmationDialog.addCommand(_noCommand);
					_confirmationDialog.addCommand(_backCommand);
					_confirmationDialog.setCommandListener(this);
					Display.getDisplay(_midlet).setCurrent(_confirmationDialog);
				}
				else {
					// Close and release the settings view
					Display.getDisplay(_midlet).setCurrent(this);
					_settingsView = null;
				}
			}
			else if (displayable == _aboutView) {
				// Close and release the about view
				Display.getDisplay(_midlet).setCurrent(this);
				_aboutView = null;
			}
			
			repaint();
		}
		else if (command == _settingsCommand) {
			if (_gameView != null && _gameView.gameOngoing()) {
				// Show the confirmation dialog
				_confirmationDialog = new Alert(GAME_ONGOING_TITLE);
				_confirmationDialog.setString("Changing the settings will end the current game. Continue?");
				_confirmationDialog.addCommand(_okCommand);
				_confirmationDialog.addCommand(_cancelCommand);
				_confirmationDialog.setCommandListener(this);
				Display.getDisplay(_midlet).setCurrent(_confirmationDialog);
			}
			else {
				showSettingsView();
			}
		}
		else if (command == _aboutCommand) {
			if (_aboutView == null) {
				_aboutView = new AboutView();
				_aboutView.addCommand(_exitAndBackCommand);
				_aboutView.setCommandListener(this);	
			}
			
			Display.getDisplay(_midlet).setCurrent(_aboutView);
		}
		else if (command == _okCommand) {
			// The user accepts that the game will be ended if the settings
			// will be changed
			showSettingsView();
		}
		else if (command == _cancelCommand || command == _noCommand) {
			// The user doesn't accept that the game will be ended or the user
			// answered "no" to apply settings
			if (_confirmationDialog != null
					&& displayable == _confirmationDialog)
			{
				// Hide the dialog
				Display.getDisplay(_midlet).setCurrent(this);
				
				if (_confirmationDialog.getTitle() == SETTINGS_CHANGED_TITLE
						&& _settingsView != null)
				{
					// Release the settings view
					_settingsView = null;
				}
				
				_confirmationDialog = null;
			}
		}
		else if (command == _yesCommand) {
			// The user answered "yes" to apply settings
			applySettingsAndShowMainView();
		}
		else if (command == _backCommand) {
			// The user answered "back" to apply settings
			if (_confirmationDialog != null && _settingsView != null) {
				_confirmationDialog = null;
				Display.getDisplay(_midlet).setCurrent(_settingsView);
			}
		}
	}


	// Methods from Connection.Listener ->
	
	public void onStateChanged(int state) {
		switch (state) {
		case Connection.DISCOVERING: {
			_progressBar.start(ConnectionManager.DISCOVERY_TIMEOUT);
			setState(CONNECTING_DIALOG);			
			break;
		}
		case Connection.CONNECTED: {
			Display.getDisplay(_midlet).setCurrent(_gameView);
			break;
		}
		case Connection.NOT_CONNECTED: {
			// Disconnected
			if (_state == CONNECTING_DIALOG && _latestErrorMessage != null) {
				_progressBar.cancel();
				_textLabel.setText("Connection error: " + _latestErrorMessage);
				setState(CONNECTION_ERROR_DIALOG);
			}
			
			break;
		}
		}
		
		_latestErrorMessage = null;
	}
	
	public void onPeerDisconnected() {
		Display.getDisplay(_midlet).setCurrent(this);
		_textLabel.setText("Connection closed. Try reconnecting?");
		setState(CONNECTION_ERROR_DIALOG);
	}
	
	public void onTimeout() {
		_textLabel.setText("No players found");
		_progressBar.setColor(0xffee2222);
		_progressBar.setValue(TimedProgressBar.MAX_VALUE);
		_progressBar.cancel();
		setState(CONNECTION_ERROR_DIALOG);
	}
	
	public void onError(String errorMessage) {
		if (_state == CONNECTING_DIALOG) {
			// Store the latest error message
			_latestErrorMessage = errorMessage;
		}
		else {
			if (Display.getDisplay(_midlet).getCurrent() != this) {
				Display.getDisplay(_midlet).setCurrent(this);
			}
			
			_textLabel.setText("Connection error");
			setState(CONNECTION_ERROR_DIALOG);
		}
	}
	
	public void onMessageSent(byte[] message) {}
	public void onReceived(byte[] message) {}
	
	// <- methods from Connection.Listener
	
	/**
	 * Initializes the application. The splash screen should be shown before
	 * calling this method since initializing the app may take some time.
	 */
	public void init() {
		// Create the background images
		try {
			_backgroundImage = Image.createImage("/graphics/main-view-bg.png");
			_dimmedBackgroundImage = Image.createImage("/graphics/dimmed-bg.png");
		} catch (IOException e) {}

		_textLabel = new TextLabel(getWidth() - MARGIN * 2, TEXT_LABEL_HEIGHT);
		_textLabel.setTextColor(0xffffffff);
		_textLabel.setAlignment(TextLabel.CENTER);
		_textLabel.setParent(this);
		_textLabel.setPosition(MARGIN, MARGIN * 3);
		_textLabel.setVisible(true);
		
		_progressBar = new TimedProgressBar(this, PROGRESS_BAR_WIDTH);
		_progressBar.setParent(this);
		_progressBar.setPosition((getWidth() - PROGRESS_BAR_WIDTH) / 2, getHeight() / 3);
		
		_console = new Console(getWidth(), getHeight());
		_console.setParent(this);
		_console.setPosition(0, 0);
		
		// At this stage we can consider the view initialized so that it can
		// be painted
		_initialized = true;
		
		// Setup the UI (create the buttons etc.)
		setState(MAIN_MENU);
		
		// Get the connection manager instance and start listening for
		// connection events
		_connectionManager = ConnectionManager.instance(this);
		DiscoveryClient.setAppIdentifier(APP_IDENTIFIER);
		
		// Initialize the game view
		_gameView.init();
	}
	
	/**
	 * Sets the UI up based on the given state. Creates the buttons according to
	 * the given state.
	 * @param state The state to set.
	 * @param forced If true, will set the state even if it's the same as the
	 * 				 current one.
	 */
	private void setState(final int state, boolean forced) {
		if (_state == state && !forced) {
			return;
		}
		
		System.out.println("MainView::setState(): " + state);
		
		if (_state == CONNECTING_DIALOG && state != CONNECTING_DIALOG) {
			// Restore the commands
			addCommand(_exitAndBackCommand);
			addCommand(_settingsCommand);
			addCommand(_aboutCommand);
		}
		
		_state = state;
		_progressBar.setVisible(false);
		
		// Clear the current buttons
		for (int i = 0; i < BUTTON_COUNT; ++i) {
			_buttons[i] = null;
		}
		
		switch (_state) {
		case MAIN_MENU: {
			_textLabel.setText(null);
			
			if (_gameView.gameOngoing()) {
				_buttons[0] = new Button("Resume");
			}
			
			_buttons[1] = new Button("Start Two Player Game");
			_buttons[2] = new Button("Start One Player Game");
			break;
		}
		case CONNECTING_DIALOG: {
			removeCommand(_exitAndBackCommand);
			removeCommand(_settingsCommand);
			removeCommand(_aboutCommand);
			
			_textLabel.setText("Looking for other players...");
			_progressBar.setValue(0);
			_progressBar.setColor(ProgressBar.DEFAULT_PROGRESS_BAR_COLOR);
			_progressBar.setVisible(true);
			_buttons[2] = new Button("Cancel");
			_buttons[2].setIsNegative(true);
			break;
		}
		case CONNECTION_ERROR_DIALOG: {
			_buttons[1] = new Button("Retry");
			_buttons[2] = new Button("Cancel");
			_buttons[2].setIsNegative(true);
			break;
		}
		}
		
		_lastButtonPressed = UNDEFINED;
		repaint();
	}
	
	/**
	 * For convenience.
	 */
	public void setState(int state) {
		setState(state, false);
	}
	
	/**
	 * Resolves the index of the button in the given coordinates.
	 * @param x The X coordinate.
	 * @param y The Y coordinate.
	 * @return The button index or -1 if no button.
	 */
	private int buttonAt(int x, int y) {
		final int buttonX = (getWidth() - Button.DEFAULT_WIDTH) / 2;
		
		if (x < buttonX || x > buttonX + Button.DEFAULT_WIDTH) {
			return UNDEFINED;
		}
		
		if (y < _buttonY) {
			return UNDEFINED;
		}
		
		int buttonY = _buttonY;
		
		for (int i = 0; i < BUTTON_COUNT; ++i) {
			if (_buttons[i] != null
					&& y >= buttonY
					&& y <= buttonY + Button.DEFAULT_HEIGHT)
			{
				return i;
			}
			
			buttonY += BUTTON_SPACING;
		}
		
		return UNDEFINED;
	}
	
	/**
	 * Takes actions based on the current state and the button clicked.
	 * @param index The index of the button that was clicked.
	 */
	private void handleButtonClicked(int index) {
		switch (_state) {
		case MAIN_MENU: {
			switch (index) {
			case 0: // Resume
				// Nothing to do here
				break;
			case 1: // Start 2P game
				_gameView.setGameManager(AbstractGameManager.NETWORK);
				_gameView.gameManager().startGame();
				setState(CONNECTING_DIALOG);
				return; // Do not switch to game view yet
			case 2: // Start 1P game
				_gameView.setGameManager(AbstractGameManager.BASIC);
				_gameView.gameManager().startGame();
				break;
			}
			
			// Show the game view
			Display.getDisplay(_midlet).setCurrent(_gameView);
			break;
		}
		case CONNECTING_DIALOG: {
			// Cancel
			_connectionManager.disconnect();
			_progressBar.cancel();
			setState(MAIN_MENU);
			return;
		}
		case CONNECTION_ERROR_DIALOG: {
			switch (index) {
			case 1: // Retry
				_gameView.gameManager().startGame();
				setState(CONNECTING_DIALOG);
				return;
			case 2: // Cancel
				setState(MAIN_MENU);
				return;
			}
			break;
		}
		}
	}
	
	/**
	 * Displays the settings view.
	 */
	private void showSettingsView() {
		if (_settingsView == null) {
			_settingsView = new SettingsView("Settings", _midlet);
			_settingsView.addCommand(_exitAndBackCommand);
			_settingsView.setCommandListener(this);
			_settingsView.setShowConsole(_console.isVisible());
		}
		
		Display.getDisplay(_midlet).setCurrent(_settingsView);
	}
	
	/**
	 * Applies the settings and displays the main view. Note that if the
	 * settings have been changed and a game is ongoing, the game is ended.
	 */
	private void applySettingsAndShowMainView() {
		if (_settingsView != null) {
			if (_gameView.gameOngoing() && _settingsView.settingsChanged()) {
				_gameView.gameManager().endGame();
			}
			
			if (!_settingsView.applySettings()) {
				return;
			}
			
			_console.setVisible(_settingsView.showConsole());
		}
		
		setState(MAIN_MENU, true);
		Display.getDisplay(_midlet).setCurrent(this);
		_settingsView = null;
	}
}
